
# coding:utf-8
import os
import json
import _thread
from tkinter.tix import Tree
from matplotlib.pyplot import flag

from sqlalchemy import false, modifier, true
from sympy import arg


# 用 user_secrets 表示 user
_user_secrets = '12345678'
_transaction_id = 0

_transaction_global_lock = _thread.allocate_lock()

# key: (transactionId, table_name)
# value: list: [时间戳]
_transaction_table_writes = {}

_transaction_table_timeStamp = {}

# 读事务 dict
# key: transactionId
# value:
_transaction_reads = {}

# 写事务的 dict
# key: transcationId
# value: list[]
_transaction_writes = {}

# key: transactionId
# value: list {[] list: [function, args]}
_transaction_modifies = {}


class Authority:
    def __init__(self):
        self.db_userAccess = {}
        self.db_tableAccess = {}

        # key:user -> list(table)
        # self.userAccessTables = {}
        # # key:(table,user) -> RW
        # self.tableAccessedUsers = {}

        self.recover()

        print(self.db_userAccess)
        print(self.db_tableAccess)

    def isAccessible(self, user, db_id, table, accessType):
        flag = False
        userAccessTables = self.db_userAccess.get(db_id, {})
        tableAccessedUsers = self.db_tableAccess.get(db_id, {})

        for t in userAccessTables.get(user, []):
            if t == table:
                flag = True
                break
        if not flag:
            return False
        for _user, rw in tableAccessedUsers.get(table, []):
            if _user == user and rw & accessType:
                return True
        return False

    def setAccess(self, user, db_id, table, accessType):

        userAccessTables = self.db_userAccess.get(db_id, {})
        tableAccessedUsers = self.db_tableAccess.get(db_id, {})

        u_tables = userAccessTables.get(user, [])
        flag = False
        for t in u_tables:
            if t == table:
                flag = True
                break
        if not flag:
            u_tables.append(table)
            userAccessTables[user] = u_tables

        flag = False
        t_users = tableAccessedUsers.get(table, [])
        for idx, [_user, rw] in enumerate(t_users):
            if _user == user:
                t_users[idx][1] = accessType
                tableAccessedUsers[table] = t_users
                flag = True
                break
        if not flag:
            t_users.append([user, accessType])
            tableAccessedUsers[table] = t_users
        #
        self.db_userAccess[db_id] = userAccessTables
        self.db_tableAccess[db_id] = tableAccessedUsers
        self.flush()

    def recover(self, root_dir="meta"):
        # 应该存在某个文件中
        db_list = ["2018年各校招生计划", "AI峰会", "CCTV中国经济年度人物"]
        for db_id in db_list:
            with open(os.path.join('db_files', db_id, root_dir, "userAccessTables.json"), 'r', encoding='UTF-8') as fp:
                data = json.load(fp)
                self.db_userAccess[db_id] = data

            with open(os.path.join('db_files', db_id, root_dir, "tableAccessedUsers.json"), 'r', encoding='UTF-8') as fp:
                data = json.load(fp)
                self.db_tableAccess[db_id] = data

    def flush(self, root_dir="meta"):
        for db_id in self.db_tableAccess:
            userAccessTables = self.db_userAccess[db_id]
            tableAccessedUsers = self.db_tableAccess[db_id]

            with open(os.path.join('db_files', db_id, root_dir, "userAccessTables.json"), 'w', encoding='UTF-8') as fp:
                json.dump(userAccessTables, fp, indent=4,
                          sort_keys=True, ensure_ascii=False)
            with open(os.path.join('db_files', db_id, root_dir, "tableAccessedUsers.json"), 'w', encoding='UTF-8') as fp:
                json.dump(tableAccessedUsers, fp, indent=4,
                          sort_keys=True, ensure_ascii=False)


authority = Authority()


def create_transaction(user_secrets, db_id, table_users):
    global _transaction_id

    # 不考虑多线程？？
    _transaction_id += 1
    transaction_id = _transaction_id
    return transaction_id


def abort_transaction(user_secrets, transaction_id):

    return True


def commit_transaction(user_secrets, transaction_id):
    print("52 hee")
    succ = True
    print("53 hee")
    _transaction_global_lock.acquire()

    print("55 hee")
    reads = _transaction_reads.get(transaction_id, [])
    for (db_id, table_name, timestamp) in reads:
        table_writes = _transaction_table_writes.get((db_id, table_name), [])
        if len(table_writes) > 0 and table_writes[-1] > timestamp:
            succ = False
    if succ:
        print("6111")
        for T in _transaction_modifies[transaction_id]:
            table_name = T[2]
            table_writes = _transaction_table_writes.get(
                (db_id, table_name), [])
            timestamp = 1 if len(table_writes) == 0 else table_writes[-1] + 1
            table_writes.append(timestamp)
            _transaction_table_writes[(db_id, table_name)] = table_writes

            opFunc = T[0]
            print("func:", opFunc)
            opFunc(T[1:])

    _transaction_global_lock.release()
    return succ


def _commit_transaction(user_secrets, transaction_id):
    succ = True
    _transaction_global_lock.acquire()
    reads = _transaction_reads.get(transaction_id, [])
    for (db_id, table_name, timestamp) in reads:
        table_writes = _transaction_table_writes.get((db_id, table_name), [])
        if len(table_writes) > 0 and table_writes[-1] > timestamp:
            succ = False
    if succ:
        for (db_id, table_name, table_data) in _transaction_writes[transaction_id]:
            table_writes = _transaction_table_writes.get(
                (db_id, table_name), [])
            timestamp = 1 if len(table_writes) == 0 else table_writes[-1] + 1
            table_writes.append(timestamp)
            _transaction_table_writes[(db_id, table_name)] = table_writes
            # 写入磁盘

            _write_table(db_id, table_name, table_data)

    _transaction_global_lock.release()
    return succ


def get_db_schema(user_secrets, transaction_id, db_id):
    #assert user_secrets == _user_secrets
    with open(os.path.join('db_files', db_id, '__schema__.json'), encoding='UTF-8') as fp:
        return json.load(fp)


_READ = 1
_WRITE = 3


def read_table(user_secrets, transaction_id, db_id, table_name):
    if not authority.isAccessible(user_secrets, db_id, table_name, _READ):
        return []

    _transaction_global_lock.acquire()
    table_writes = _transaction_table_writes.get((db_id, table_name), [])
    timestamp = 0 if len(table_writes) == 0 else table_writes[-1]
    reads = _transaction_reads.get(transaction_id, [])
    reads.append((db_id, table_name, timestamp))
    _transaction_reads[transaction_id] = reads
    # read
    with open(os.path.join('db_files', db_id, f'{table_name}.json'), encoding='UTF-8') as fp:
        data = json.load(fp)
    _transaction_global_lock.release()
    return data


def write_table(user_secrets, transaction_id, db_id, table_name, table_data):
    assert authority.isAccessible(user_secrets, db_id, table_name, _WRITE)
    _transaction_global_lock.acquire()

    # writes = _transaction_writes.get(transaction_id, [])
    # writes.append((db_id, table_name, table_data))
    # _transaction_writes[transaction_id] = writes
    modifies = _transaction_modifies.get(transaction_id, [])
    modifies.append([_write_table, db_id, table_name, table_data])
    _transaction_modifies[transaction_id] = modifies

    _transaction_global_lock.release()


def insert(user_secrets, transaction_id, db_id, table_name, value):
    assert authority.isAccessible(user_secrets, db_id, table_name, _WRITE)
    _transaction_global_lock.acquire()
    # modifies = _transaction_modifies.get(transaction_id, [])
    # modifies.append([_insert, db_id, table_name, value])
    # _transaction_modifies[transaction_id] = modifies

    _insert(db_id, table_name, value)

    _transaction_global_lock.release()


def update(user_secrets, transaction_id, db_id, table_name, update_table):
    assert authority.isAccessible(user_secrets, db_id, table_name, _WRITE)
    _transaction_global_lock.acquire()
    # modifies = _transaction_modifies.get(transaction_id, [])
    # modifies.append([_update, db_id, table_name, update_table])
    # _transaction_modifies[transaction_id] = modifies
    _update(db_id, table_name, update_table)

    _transaction_global_lock.release()

    # return True


def delete(user_secrets, transaction_id, db_id, table_name, delete_table_data):
    assert authority.isAccessible(user_secrets, db_id, table_name, _WRITE)
    _transaction_global_lock.acquire()
    # modifies = _transaction_modifies.get(transaction_id, [])
    # modifies.append([_delete, db_id, table_name, delete_table_data])
    # _transaction_modifies[transaction_id] = modifies
    _delete(db_id, table_name, delete_table_data)
    _transaction_global_lock.release()


def _write_table(*args):
    db_id = args[0]
    table_name = args[1]
    table_data = args[2]
    with open(os.path.join('db_files', db_id, f'{table_name}.json'), 'w', encoding='UTF-8') as fp:
        json.dump(table_data, fp, indent=4,
                  sort_keys=True, ensure_ascii=False)


def _insert(*args):
    # TODO: 优雅的写法
    db_id = args[0]
    table_name = args[1]
    value = args[2]

    with open(os.path.join('db_files', db_id, f'{table_name}.json'), 'r+', encoding='UTF-8') as fp:
        data = json.load(fp)
        assert len(data["field_names"]) == len(value), "wanted col len is: {0}, but {1} gived".format(
            len(data["field_names"]), len(value))
        # TODO: check the type
        data["rows"].append(value)
        fp.seek(0)
        json.dump(data, fp, indent=4,
                  sort_keys=True, ensure_ascii=False)

    return True


def _update(*args):
    db_id = args[0]
    table_name = args[1]
    update_table_data = args[2]
    # 暴力实现，把 table_data 更新到 table_name 中，就是还是需要边比较再写入。
    with open(os.path.join('db_files', db_id, f'{table_name}.json'), 'r+', encoding='UTF-8') as fp:
        data = json.load(fp)
        # TODO: check the type
        data["rows"] = update_table_data
        fp.seek(0)
        json.dump(data, fp, indent=4,
                  sort_keys=True, ensure_ascii=False)


def _delete(*args):
    # 暴力实现
    db_id = args[0]
    table_name = args[1]
    delete_table_data = args[2]
    # print(delete_table_data)
    data = []
    with open(os.path.join('db_files', db_id, f'{table_name}.json'), 'r+', encoding='UTF-8') as fp:
        data = json.load(fp)
        # TODO: check the type
        data["rows"] = delete_table_data

    with open(os.path.join('db_files', db_id, f'{table_name}.json'), 'w', encoding='UTF-8') as fp:
        json.dump(data, fp, indent=4,
                  sort_keys=True, ensure_ascii=False)


class T_LOG:
    @staticmethod
    def logs():
        pass

    @staticmethod
    def T_start(transaction_id):
        pass

    @staticmethod
    def T_COMMIT(transaction_id):
        pass

    @staticmethod
    def T_ABORT(transaction_id):
        pass

    @staticmethod
    def T_ACTION(transaction_id, Xi, V_OLD, V_NEW):
        pass

    @staticmethod
    def UNDO(transaction_id):
        pass

    @staticmethod
    def REDO(transaction_id):
        pass
